﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Cci;
using Microsoft.Cci.MutableCodeModel;
using Microsoft.Cci.Contracts;


namespace Mutator
{
    class Mutants : Microsoft.Cci.MutableCodeModel.CodeMutator
    {

        public static int mutantNumber = 0;

        public ExpressionMutantGenerator expressionMutantGenerator;
        public ExpressionMutantRetriever expressionMutantRetriever;
        public ConditionMutantGenerator conditionMutantGenerator;
        public OriginalVisitor originalVisitor;
        // public SourceRelatedVisitor sourceVisitor;


        public static ITypeDefinition addedType;
        public int fieldCount;

        bool doMutate = true;
        bool isTypeAdded = false;
        bool isFieldsAdded = false;
        bool isConstructorAdded = false;

        public Mutants(IMetadataHost host)
            : base(host)
        {

        }
        public Mutants(IMetadataHost host, ISourceLocationProvider/*?*/ sourceLocationProvider)
            : base(host, sourceLocationProvider)
        {

        }
        // add a class to contain the fields we need to record mutant information
        public override List<INamespaceMember> Visit(List<INamespaceMember> namespaceMembers)
        {
            if (this.stopTraversal) return namespaceMembers;
            if (!isTypeAdded)
            {
                var testClass = new NamespaceTypeDefinition()
                {
                    ContainingUnitNamespace = this.GetCurrentNamespace(),
                    InternFactory = host.InternFactory,
                    IsClass = true,
                    IsPublic = true,
                    IsStatic = true,
                    Name = this.host.NameTable.GetNameFor("ClassAddedByPexMutator"),
                };
                testClass.BaseClasses.Add(this.host.PlatformType.SystemObject);
                namespaceMembers.Insert(0, testClass);
                isTypeAdded = true;
            }
            for (int i = 0, n = namespaceMembers.Count; i < n; i++)
                namespaceMembers[i] = this.Visit(namespaceMembers[i]);
            return namespaceMembers;
        }

        public override List<IMethodDefinition> Visit(List<IMethodDefinition> methodDefinitions)
        {

            if (this.stopTraversal) return methodDefinitions;

            TypeDefinition curType = this.GetCurrentType() as TypeDefinition;

            //Console.WriteLine(curType.Name+":"+addedType.Name);
            if (!isConstructorAdded)
            {
                //Console.WriteLine(this.GetCurrentType().ToString());
                MethodDefinition cctorDefinition = new MethodDefinition();
                cctorDefinition.Name = this.host.NameTable.Cctor;

                cctorDefinition.Visibility = TypeMemberVisibility.Public;
                cctorDefinition.IsRuntimeSpecial = true;
                cctorDefinition.IsCil = true;

                cctorDefinition.IsStatic = true;
                cctorDefinition.Type = this.host.PlatformType.SystemVoid;
                cctorDefinition.IsSpecialName = true;
                cctorDefinition.ContainingTypeDefinition = this.GetCurrentType();
                // cctorDefinition.CallingConvention = CallingConvention.HasThis;
                SourceMethodBody body = new SourceMethodBody(host, null, null)
                {
                    MethodDefinition = cctorDefinition,
                    LocalsAreZeroed = true
                };
                cctorDefinition.Body = body;
                body.Block = new BlockStatement();
                methodDefinitions.Add(cctorDefinition);
                isConstructorAdded = true;
            }
            //hasCctor = false;
            for (int i = 0, n = methodDefinitions.Count; i < n; i++)
                methodDefinitions[i] = this.Visit(methodDefinitions[i]);
            return methodDefinitions;
        }

        public override List<IFieldDefinition> Visit(List<IFieldDefinition> fieldDefinitions)
        {

            TypeDefinition curType = this.GetCurrentType() as TypeDefinition;
            if (!isFieldsAdded)
            {
                FieldDefinition executedField = new FieldDefinition();
                executedField.Name = this.host.NameTable.GetNameFor("pexExecutedMutants");
                executedField.Type = (this.host as HostEnvironment).MyPlatformType.SystemIOWriter;
                executedField.Visibility = TypeMemberVisibility.Public;
                executedField.IsStatic = true;

                FieldDefinition weakKilledField = new FieldDefinition();
                weakKilledField.Name = this.host.NameTable.GetNameFor("pexWeakKilledMutants");
                weakKilledField.Type = (this.host as HostEnvironment).MyPlatformType.SystemIOWriter;
                weakKilledField.Visibility = TypeMemberVisibility.Public;
                weakKilledField.IsStatic = true;

                fieldDefinitions.Add(executedField);
                fieldDefinitions.Add(weakKilledField);


                addedType = this.GetCurrentType();
                fieldCount = this.GetCurrentType().Fields.Count();
                isFieldsAdded = true;
            }


            for (int i = 0, n = fieldDefinitions.Count; i < n; i++)
                fieldDefinitions[i] = this.Visit(fieldDefinitions[i]);
            return fieldDefinitions;
        }


        public override IMethodBody Visit(IMethodBody methodBody)
        {

            ISourceMethodBody sourceMethodBody = methodBody as ISourceMethodBody;


            if (sourceMethodBody != null)
            {
                SourceMethodBody mutableSourceMethodBody = new SourceMethodBody(this.host, this.sourceLocationProvider, null);

                BlockStatement blockStatement = new BlockStatement();

                TypeDefinition curType = this.GetCurrentType() as TypeDefinition;

                if (this.GetCurrentMethod().Name.Value.Equals(".cctor") && curType.Equals(addedType))
                {
                    Console.WriteLine();
                    Console.WriteLine("========================Mutation Point Record==========================");
                    //Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory);
                    CreateObjectInstance creIns1 = new CreateObjectInstance();
                    IMethodReference methodRef1 = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.Ctor, 0, this.host.PlatformType.SystemString, this.host.PlatformType.SystemBoolean);
                    creIns1.MethodToCall = methodRef1;
                    creIns1.Arguments.Add(new CompileTimeConstant() { Value = "c:\\MutantExecuted", Type = this.host.PlatformType.SystemString });
                    creIns1.Arguments.Add(new CompileTimeConstant() { Value = true, Type = this.host.PlatformType.SystemBoolean });
                    //Console.WriteLine("++++++ HH:"+addedType.Fields.Count);
                    Assignment assignment1 = new Assignment() { Target = new TargetExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 2) }, Source = creIns1 };
                    ExpressionStatement iniFiled1 = new ExpressionStatement() { Expression = assignment1 };
                    blockStatement.Statements.Add(iniFiled1);



                    CreateObjectInstance creIns2 = new CreateObjectInstance();
                    IMethodReference methodRef2 = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.Ctor, 0, this.host.PlatformType.SystemString, this.host.PlatformType.SystemBoolean);
                    creIns2.MethodToCall = methodRef2;
                    creIns2.Arguments.Add(new CompileTimeConstant() { Value = "c:\\MutantWeakKilled", Type = this.host.PlatformType.SystemString });
                    creIns2.Arguments.Add(new CompileTimeConstant() { Value = true, Type = this.host.PlatformType.SystemBoolean });
                    Assignment assignment2 = new Assignment() { Target = new TargetExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 1) }, Source = creIns2 };
                    ExpressionStatement iniFiled2 = new ExpressionStatement() { Expression = assignment2 };
                    blockStatement.Statements.Add(iniFiled2);

                }

                blockStatement.Statements.AddRange(sourceMethodBody.Block.Statements);
                mutableSourceMethodBody.Block = this.Visit(blockStatement);

                mutableSourceMethodBody.MethodDefinition = this.GetCurrentMethod();
                mutableSourceMethodBody.LocalsAreZeroed = methodBody.LocalsAreZeroed;
                return mutableSourceMethodBody;
            }
            return base.Visit(methodBody);
        }

        // Bug fixed: we should not move the local variable declaration to a inner block.
        /*public override IStatement Visit(LocalDeclarationStatement localDeclarationStatement)
        {

            if (doMutate)
            {
                BlockStatement wholeBlock = this.generateExpressionMutants(localDeclarationStatement);
                wholeBlock.Statements.Add(localDeclarationStatement);
                doMutate = false;
                IBlockStatement returnBlock = this.Visit(wholeBlock);
                doMutate = true;
                return returnBlock;
            }
            else
            {
                localDeclarationStatement.LocalVariable = this.Visit(this.GetMutableCopy(localDeclarationStatement.LocalVariable));
                if (localDeclarationStatement.InitialValue != null)
                    localDeclarationStatement.InitialValue = Visit(localDeclarationStatement.InitialValue);
                return localDeclarationStatement;
            }

        }*/
        
        public override IStatement Visit(ReturnStatement returnStatement)
        {
            if (doMutate)
            {
                BlockStatement wholeBlock = this.generateExpressionMutants(returnStatement);
                wholeBlock.Statements.Add(returnStatement);
                doMutate = false;
                IBlockStatement returnBlock = this.Visit(wholeBlock);
                doMutate = true;
                return returnBlock;
            }
            else
            {
                if (returnStatement.Expression != null)
                    returnStatement.Expression = Visit(returnStatement.Expression);
                return returnStatement;
            }
        }
        public override IStatement Visit(ExpressionStatement expressionStatement)
        {

            if (doMutate)
            {
                BlockStatement wholeBlock = this.generateExpressionMutants(expressionStatement);
                wholeBlock.Statements.Add(expressionStatement);
                doMutate = false;
                IBlockStatement returnBlock = this.Visit(wholeBlock);
                doMutate = true;
                return returnBlock;
            }
            else
            {
                expressionStatement.Expression = base.Visit(expressionStatement.Expression);
                return expressionStatement;
            }

        }
        public override IStatement Visit(WhileDoStatement whileDoStatement)
        {
            if (doMutate)
            {
                BlockStatement wholeBlock = this.generateConditionalMutants(whileDoStatement);
                whileDoStatement.Body = Visit(whileDoStatement.Body);
                wholeBlock.Statements.Add(whileDoStatement);
                doMutate = false;
                IBlockStatement returnBlock = this.Visit(wholeBlock);
                doMutate = true;
                return returnBlock;
            }
            else
            {
                whileDoStatement.Condition = Visit(whileDoStatement.Condition);
                whileDoStatement.Body = Visit(whileDoStatement.Body);
                return whileDoStatement;
            }
        }
        public override IStatement Visit(ForStatement forStatement)
        {
            if (doMutate)
            {
                BlockStatement wholeBlock = this.generateConditionalMutants(forStatement);
                wholeBlock.Statements.Add(forStatement);
                doMutate = false;
                IBlockStatement returnBlock = this.Visit(wholeBlock);
                doMutate = true;
                return wholeBlock;
            }
            else
            {
                forStatement.InitStatements = Visit(forStatement.InitStatements);
                forStatement.Condition = Visit(forStatement.Condition);
                forStatement.IncrementStatements = Visit(forStatement.IncrementStatements);
                forStatement.Body = Visit(forStatement.Body);
                return forStatement;
            }
        }
        public override IStatement Visit(ConditionalStatement conditionalStatement)
        {
            //generate mutated conditions        
            if (doMutate)
            {
                BlockStatement wholeBlock = this.generateConditionalMutants(conditionalStatement);
                conditionalStatement.TrueBranch = Visit(conditionalStatement.TrueBranch);
                conditionalStatement.FalseBranch = Visit(conditionalStatement.FalseBranch);
                wholeBlock.Statements.Add(conditionalStatement);
                doMutate = false;
                IBlockStatement returnBlock = this.Visit(wholeBlock);
                doMutate = true;
                return returnBlock;
            }
            else
            {
                conditionalStatement.Condition = Visit(conditionalStatement.Condition);
                conditionalStatement.TrueBranch = Visit(conditionalStatement.TrueBranch);
                conditionalStatement.FalseBranch = Visit(conditionalStatement.FalseBranch);

                return conditionalStatement;
            }
        }
        public BlockStatement generateExpressionMutants(IStatement statement)
        {
            BlockStatement wholeBlock = new BlockStatement();
            expressionMutantGenerator.list.Clear();
            expressionMutantGenerator.Visit(statement);
            for (int i = 0; i < expressionMutantGenerator.list.Count; i++)
            {
                Pair pair = expressionMutantGenerator.list.ElementAt(i);

                MethodCall exeWeakMethodCall = new MethodCall();
                Microsoft.Cci.MethodReference weakExeRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Write"), 0, this.host.PlatformType.SystemString);
                exeWeakMethodCall.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 2) };
                exeWeakMethodCall.IsStaticCall = false;
                exeWeakMethodCall.Arguments.Add(new CompileTimeConstant() { Value = ++mutantNumber + ",", Type = this.host.PlatformType.SystemString });
                exeWeakMethodCall.Type = this.host.PlatformType.SystemVoid;
                exeWeakMethodCall.MethodToCall = weakExeRef;
                ExpressionStatement addExeSta = new ExpressionStatement() { Expression = exeWeakMethodCall };

                MethodCall flushExe = new MethodCall();
                Microsoft.Cci.MethodReference flushExeRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Flush"), 0);
                flushExe.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 2) };
                flushExe.IsStaticCall = false;
                flushExe.Type = this.host.PlatformType.SystemVoid;
                flushExe.MethodToCall = flushExeRef;
                ExpressionStatement flushExeSta = new ExpressionStatement() { Expression = flushExe };


                ConditionalStatement killedStatement = new ConditionalStatement();
                NotEquality killedExpression = new NotEquality();
                killedExpression.LeftOperand = pair.original;
                killedExpression.RightOperand = pair.mutated;
                Division div = pair.mutated as Division;
                Modulus mod = pair.mutated as Modulus;

                //  Introduction of these branches aims to avoid the divide-by-0 exception in the instrumented code.
                if (div != null)
                {
                    Conditional isZero = new Conditional();
                    Equality zero = new Equality();
                    zero.LeftOperand = div.RightOperand;
                    zero.RightOperand = new CompileTimeConstant() { Value = 0, Type = div.RightOperand.Type };
                    isZero.Condition = zero;
                    isZero.ResultIfTrue = new CompileTimeConstant() { Value = true, Type = this.host.PlatformType.SystemBoolean };

                    isZero.ResultIfFalse = killedExpression;

                    killedStatement.Condition = isZero;
                }
                else if (mod != null)
                {
                    Conditional isZero = new Conditional();
                    Equality zero = new Equality();
                    zero.LeftOperand = mod.RightOperand;
                    zero.RightOperand = new CompileTimeConstant() { Value = 0, Type = mod.RightOperand.Type };
                    isZero.Condition = zero;
                    isZero.ResultIfTrue = new CompileTimeConstant() { Value = true, Type = this.host.PlatformType.SystemBoolean };

                    isZero.ResultIfFalse = killedExpression;

                    killedStatement.Condition = isZero;
                }
                else
                {
                    killedStatement.Condition = killedExpression;
                }


                MethodCall methodCall = new MethodCall();
                Microsoft.Cci.MethodReference weakAddRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Write"), 0, this.host.PlatformType.SystemString);
                methodCall.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 1) };
                methodCall.IsStaticCall = false;
                methodCall.Arguments.Add(new CompileTimeConstant() { Value = mutantNumber + ",", Type = this.host.PlatformType.SystemString });
                methodCall.Type = this.host.PlatformType.SystemVoid;
                methodCall.MethodToCall = weakAddRef;
                ExpressionStatement addKilledWeak = new ExpressionStatement() { Expression = methodCall };

                MethodCall flushWeak = new MethodCall();
                Microsoft.Cci.MethodReference flushWeakRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Flush"), 0);
                flushWeak.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 1) };
                flushWeak.IsStaticCall = false;
                flushWeak.Type = this.host.PlatformType.SystemVoid;
                flushWeak.MethodToCall = flushWeakRef;
                ExpressionStatement flushWeakSta = new ExpressionStatement() { Expression = flushWeak };

                BlockStatement trueBranch = new BlockStatement();
                trueBranch.Statements.Add(addKilledWeak);
                trueBranch.Statements.Add(flushWeakSta);

                killedStatement.TrueBranch = trueBranch;
                killedStatement.FalseBranch = new EmptyStatement();
                wholeBlock.Statements.Add(addExeSta);
                wholeBlock.Statements.Add(flushExeSta);
                wholeBlock.Statements.Add(killedStatement);
            }
            return wholeBlock;

        }
        public BlockStatement generateConditionalMutants(IStatement statement)
        {
            conditionMutantGenerator.list.Clear();

            IConditionalStatement conditional = statement as ConditionalStatement;
            IWhileDoStatement whiledo = statement as WhileDoStatement;
            IForStatement forsta = statement as ForStatement;
            if (conditional != null)
                conditionMutantGenerator.Visit(conditional.Condition);
            else if (whiledo != null)
                conditionMutantGenerator.Visit(whiledo.Condition);
            else if (forsta != null)
                conditionMutantGenerator.Visit(forsta.Condition);
            BlockStatement wholeBlock = new BlockStatement();
            for (int i = 0; i < conditionMutantGenerator.list.Count; i++)
            {
                Pair pair = conditionMutantGenerator.list.ElementAt(i);
                MethodCall exeWeakMethodCall = new MethodCall();
                Microsoft.Cci.MethodReference weakExeRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Write"), 0, this.host.PlatformType.SystemString);
                exeWeakMethodCall.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 2) };
                exeWeakMethodCall.IsStaticCall = false;
                exeWeakMethodCall.Arguments.Add(new CompileTimeConstant() { Value = ++mutantNumber + ",", Type = this.host.PlatformType.SystemString });
                exeWeakMethodCall.Type = this.host.PlatformType.SystemVoid;
                exeWeakMethodCall.MethodToCall = weakExeRef;
                ExpressionStatement addExeSta = new ExpressionStatement() { Expression = exeWeakMethodCall };

                MethodCall flushExe = new MethodCall();
                Microsoft.Cci.MethodReference flushExeRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Flush"), 0);
                flushExe.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 2) };
                flushExe.IsStaticCall = false;
                flushExe.Type = this.host.PlatformType.SystemVoid;
                flushExe.MethodToCall = flushExeRef;
                ExpressionStatement flushExeSta = new ExpressionStatement() { Expression = flushExe };


                ConditionalStatement killedStatement = new ConditionalStatement();
                LogicalNot negOri = new LogicalNot();
                negOri.Operand = pair.original;
                LogicalNot negMut = new LogicalNot();
                negMut.Operand = pair.mutated;

                Conditional killedExpression = new Conditional();

                killedExpression.Condition = pair.original;
                killedExpression.ResultIfTrue = negMut;

                Conditional anotherKilledExpression = new Conditional();
                anotherKilledExpression.Condition = negOri;
                anotherKilledExpression.ResultIfFalse = new CompileTimeConstant() { Value = false, Type = this.host.PlatformType.SystemBoolean };
                anotherKilledExpression.ResultIfTrue = pair.mutated;

                killedExpression.ResultIfFalse = anotherKilledExpression;
                killedStatement.Condition = killedExpression;

                BlockStatement trueBranch = new BlockStatement();
                MethodCall methodCall = new MethodCall();
                Microsoft.Cci.MethodReference weakAddRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Write"), 0, this.host.PlatformType.SystemString);
                methodCall.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 1) };
                methodCall.IsStaticCall = false;
                methodCall.Arguments.Add(new CompileTimeConstant() { Value = mutantNumber + ",", Type = this.host.PlatformType.SystemString });
                methodCall.Type = this.host.PlatformType.SystemVoid;
                methodCall.MethodToCall = weakAddRef;
                ExpressionStatement addKilledSta = new ExpressionStatement() { Expression = methodCall };

                MethodCall flushKilled = new MethodCall();
                Microsoft.Cci.MethodReference flushWeakRef = new Microsoft.Cci.MethodReference(this.host, (this.host as HostEnvironment).MyPlatformType.SystemIOWriter, CallingConvention.HasThis, this.host.PlatformType.SystemVoid, this.host.NameTable.GetNameFor("Flush"), 0);
                flushKilled.ThisArgument = new BoundExpression() { Definition = addedType.Fields.ElementAt(fieldCount - 1) };
                flushKilled.IsStaticCall = false;
                flushKilled.Type = this.host.PlatformType.SystemVoid;
                flushKilled.MethodToCall = flushWeakRef;
                ExpressionStatement flushKilledSta = new ExpressionStatement() { Expression = flushKilled };
                trueBranch.Statements.Add(addKilledSta);
                trueBranch.Statements.Add(flushKilledSta);
                killedStatement.TrueBranch = trueBranch;
                killedStatement.FalseBranch = new EmptyStatement();

                wholeBlock.Statements.Add(addExeSta);
                wholeBlock.Statements.Add(flushExeSta);
                wholeBlock.Statements.Add(killedStatement);
            }
            return wholeBlock;
        }

    }


}
